<!DOCTYPE html>
<html>
  <head>
     
    <meta charset="UTF-8">
    <title>Redis2 - 测开、Java后端最常见、最核心的面试题总结</title>
    <link rel="shortcut icon" href="/static/img/icon.png">
    <link rel="icon" href="/static/img/icon.png" sizes="192x192"/>
    
<link rel="stylesheet" href="/static/kico.css">
<link rel="stylesheet" href="/static/hingle.css">

    
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">

    <meta name="viewport" content="width=device-width, maximum-scale=1, initial-scale=1"/>
    <meta property="og:site_name" content="测开、Java后端最常见、最核心的面试题总结">
    <meta property="og:title" content="Redis2"/>
    
<meta name="generator" content="Hexo 7.3.0"></head>

  <body>
    <header>
    <div class="head-title">
        <h4>测开、Java后端最常见、最核心的面试题总结</h4>
    </div>
    <div class="head-action">
        <div class="toggle-btn"></div>
        <div class="light-btn"></div>
        <div class="search-btn"></div>
    </div>
    <form class="head-search" method="post">
        <input type="text" name="s" placeholder="搜索什么？">
    </form>
    <nav class="head-menu">
        <a href="/">首页</a>
        
    </nav>
</header>

    <main>
    <div class="wrap min">
        <section class="post-title">
            <h2>Redis2</h2>
            <div class="post-meta">
                <time class="date">2024.07.25</time>
            
            </div>
        </section>
        <article class="post-content">
        
            <h1><span id="13常见的缓存读写策略有哪些">13.常见的缓存读写策略有哪些？</span></h1><p>常见的缓存读写策略主要有以下几种，每种策略适用于不同的场景和需求：</p>
<ol>
<li><p>旁路缓存模式（Cache Aside Pattern）：在旁路缓存模式中，应用程序直接与缓存和数据库交互。数据首先从缓存中读取，如果缓存未命中，则从数据库中加载数据，并将数据存入缓存。</p>
<ul>
<li>读操作：从缓存中读取数据。如果缓存命中，直接返回数据。如果缓存未命中，从数据库中查询数据，并将其写入缓存。</li>
<li>写操作：更新数据库中的记录。删除缓存中的相关数据。</li>
<li>优点：适合读请求较多的场景。可以有效减少数据库的负载。</li>
<li>缺点：可能导致缓存和数据库之间的数据不一致。</li>
</ul>
</li>
<li><p>读写穿透策略（Read&#x2F;Write Through Pattern）：在读写穿透策略中，所有的读写操作都通过缓存进行。应用程序只与缓存交互，缓存负责与数据库的通信。</p>
<ul>
<li>读操作：查询缓存，如果存在则返回；如果不存在，则从数据库中加载数据并写入缓存。</li>
<li>写操作：更新缓存中的数据，并同步更新到数据库。</li>
<li>优点：简化了应用程序的逻辑，所有操作都通过缓存进行。数据一致性较高，因为写操作直接更新了缓存和数据库。</li>
<li>缺点：如果缓存故障，可能会影响系统的可用性。</li>
</ul>
</li>
<li><p>异步缓存写入策略（Write Behind &#x2F; Write Back Pattern）：在异步缓存写入策略中，数据首先写入缓存，然后异步地将数据写入数据库。这种方式可以提高写操作的性能。</p>
<ul>
<li>读操作：从缓存中读取数据。</li>
<li>写操作：将数据写入缓存，并异步将数据写入数据库。</li>
<li>优点：提高了写入性能，减少了对数据库的直接写入压力。适合写操作频繁的场景。</li>
<li>缺点：数据一致性风险较高，可能导致数据丢失或延迟写入。</li>
</ul>
</li>
<li><p>总结：选择合适的缓存读写策略应根据具体的业务需求和场景来决定：</p>
<ul>
<li>旁路缓存模式适合读多写少的场景，可以有效降低数据库负载。</li>
<li>读写穿透策略适合需要高一致性的场景，简化了应用逻辑。</li>
<li>异步缓存写入策略适合写操作频繁的场景，但需要注意数据一致性问题。</li>
</ul>
</li>
</ol>
<p>拓展：</p>
<ol>
<li>在写数据的过程中，可以先删除缓存，后更新数据库吗？<br>不行！因为这样可能会造成 数据库和缓存数据不一致的问题。<br>举例：请求 1 先写数据 A，请求 2 随后读数据 A 的话，就很有可能产生数据不一致性的问题。</li>
<li>在写数据的过程中，先更新数据库，后删除缓存就没有问题了吗？<br>理论上来说还是可能会出现数据不一致性的问题，不过概率非常小，因为缓存的写入速度是比数据库的写入速度快很多。<br>举例：请求 1 先读数据 A，请求 2 随后写数据 A，并且数据 A 在请求 1 请求之前不在缓存中的话，也有可能产生数据不一致性的问题。</li>
</ol>
<h1><span id="14redis-使用的过期删除策略是什么">14.Redis 使用的过期删除策略是什么？</span></h1><p><strong>访问流程：</strong>每当我们对一个 key 设置了过期时间时，Redis 会把该 key 带上过期时间存储到一个<strong>过期字典</strong>中，也就是说过期字典中保存了数据库中所有 key 的过期时间。当我们查询一个 key 时，Redis 首先检查该 key 是否存在于过期字典中：</p>
<ul>
<li>如果不在，则正常读取键值；</li>
<li>如果存在，则会获取该 key 的过期时间，然后与当前系统时间进行比对，如果比系统时间大，那就没有过期，否则判定该 key 已过期。</li>
</ul>
<p>Redis 的默认配置是同时使用惰性删除和定期删除两种策略。</p>
<ol>
<li>惰性删除策略：惰性删除策略的做法是，不主动删除过期键，每次从数据库访问 key 时，都检测 key 是否过期，如果过期则删除该 key。<ul>
<li>优点：惰性删除能够节省 CPU 资源，因为只有在访问键时才会检查其过期状态。</li>
<li>缺点：如果一个 key 已经过期，而这个 key 又仍然保留在数据库中，那么只要这个过期 key 一直没有被访问，它所占用的内存就不会释放，造成了一定的内存空间浪费。</li>
</ul>
</li>
<li>定期删除策略：每隔一段时间随机从数据库中取出一定数量的 key 进行检查，并删除其中的过期key。<ul>
<li><p>流程：</p>
<ul>
<li>从过期字典中随机抽取 20 个 key；</li>
<li>检查这 20 个 key 是否过期，并删除已过期的 key；</li>
<li>如果本轮检查的已过期 key 的数量，超过 5 个（20&#x2F;4），也就是「已过期 key 的数量」占比「随机抽取 key 的数量」大于 25%，则继续重复之前的步骤 ；如果已过期的 key 比例小于 25%，则停止继续删除过期 key，然后等待下一轮再检查。</li>
</ul>
</li>
<li><p>优点：定期删除可以在一定程度上清理那些未被访问的过期键，避免内存中堆积大量的无用数据。</p>
</li>
<li><p>缺点：难以确定删除操作执行的时长和频率。如果执行的太频繁，就会对 CPU 不友好；如果执行的太少，那又和惰性删除一样了，过期 key 占用的内存不会及时得到释放。</p>
</li>
</ul>
</li>
</ol>
<h1><span id="15redis-内存淘汰策略有哪些">15.Redis 内存淘汰策略有哪些？</span></h1><p>Redis 内存淘汰策略共有八种，这八种策略大体分为「不进行数据淘汰」和「进行数据淘汰」两类策略。</p>
<p><em><strong>1、不进行数据淘汰的策略</strong></em></p>
<p><strong>noeviction</strong>（Redis3.0之后，默认的内存淘汰策略） ：它表示当运行内存超过最大设置内存时，不淘汰任何数据，而是不再提供服务，直接返回错误。</p>
<p><em><strong>2、进行数据淘汰的策略</strong></em></p>
<p>针对「进行数据淘汰」这一类策略，又可以细分为「在设置了过期时间的数据中进行淘汰」和「在所有数据范围内进行淘汰」这两类策略。 在设置了过期时间的数据中进行淘汰：</p>
<ul>
<li><strong>volatile-random</strong>：随机淘汰设置了过期时间的任意键值；</li>
<li><strong>volatile-ttl</strong>：优先淘汰更早过期的键值。</li>
<li><strong>volatile-lru</strong>（Redis3.0 之前，默认的内存淘汰策略）：淘汰所有设置了过期时间的键值中，最久未使用的键值；</li>
<li><strong>volatile-lfu</strong>：淘汰所有设置了过期时间的键值中，最少使用的键值；</li>
</ul>
<p>在所有数据范围内进行淘汰：</p>
<ul>
<li><strong>allkeys-random</strong>：随机淘汰任意键值;</li>
<li><strong>allkeys-lru</strong>：淘汰整个键值中最久未使用的键值；</li>
<li><strong>allkeys-lfu</strong>：淘汰整个键值中最少使用的键值。</li>
</ul>
<h1><span id="16如何避免缓存雪崩-缓存击穿-缓存穿透">16.如何避免缓存雪崩、缓存击穿、缓存穿透？</span></h1><ol>
<li><p>缓存雪崩：当大量缓存数据在同一时间过期时，如果此时有大量的用户请求，都无法在 Redis 中处理，于是全部请求都直接访问数据库，从而导致数据库的压力骤增，严重的会造成数据库宕机，造成整个系统崩溃。<br>解决方案：</p>
<ul>
<li><p><strong>设置随机过期时间</strong>：为缓存数据设置不同的过期时间，避免在同一时刻大量缓存失效。</p>
</li>
<li><p><strong>使用高可用的缓存架构</strong>：部署Redis主从集群和哨兵机制，确保即使某个缓存节点宕机，系统仍能正常运作。</p>
</li>
<li><p><strong>请求限流和服务降级</strong>：在高并发情况下，限制请求速率，避免数据库被瞬时大量请求压垮。</p>
</li>
</ul>
</li>
<li><p>缓存击穿：缓存击穿发生在热点数据的缓存失效时，多个请求同时访问该数据，导致直接查询数据库，可能使数据库负载过重。<br>解决方案：</p>
<ul>
<li><strong>互斥锁方案：</strong>Redis 中使用 setNX 方法设置一个状态位，表示这是一种锁定状态，保证同一时间只有一个业务线程请求缓存，未能获取互斥锁的请求，要么等待锁释放后重新读取缓存，要么就返回空值或者默认值。</li>
<li><strong>不设置过期时间</strong>：对于热点数据，可以选择不设置过期时间，确保数据一直在缓存中。</li>
<li><strong>预热缓存</strong>：在系统上线前，提前将热点数据加载到缓存中，避免首次访问时的压力</li>
</ul>
</li>
<li><p>缓存穿透：当用户访问的数据，既不在缓存中，也不在数据库中，导致请求在访问缓存时，发现缓存缺失，再去访问数据库时，发现数据库中也没有要访问的数据，没办法构建缓存数据来服务后续的请求。那么当有大量这样的请求到来时，数据库的压力骤增，这就是缓存穿透的问题。</p>
<ul>
<li>缓存穿透的发生一般有这两种情况：<br>业务误操作，缓存中的数据和数据库中的数据都被误删除了，所以导致缓存和数据库中都没有数据；<br>黑客恶意攻击，故意大量访问某些读取不存在数据的业务；</li>
<li>解决方案：<ul>
<li><strong>参数校验</strong>：对请求参数进行严格的校验，非法请求直接返回错误，避免无效请求对数据库的压力。</li>
<li><strong>缓存空值</strong>：对于查询不到的数据，可以在缓存中存储一个空值，防止相同的无效请求多次查询数据库。</li>
<li><strong>使用布隆过滤器</strong>：在请求到达数据库前，先通过布隆过滤器判断数据是否存在，避免不必要的数据库查询</li>
</ul>
</li>
</ul>
</li>
</ol>
<h1><span id="17大-key-问题">17.大 key 问题</span></h1><p>在Redis中，大key问题指的是某个键对应的数据结构（如字符串、列表、集合、哈希表等）过大，导致内存消耗过多、操作性能下降或出现阻塞的情况。</p>
<ol>
<li><p>如何找到大 key？</p>
<ul>
<li>使用<code>--bigkeys</code>参数：<code>redis-cli --bigkeys</code> 是一个Redis客户端命令，它可以帮助你扫描整个Redis数据库，找出其中的“大key”。它会分析所有类型的key（如字符串、列表、集合、有序集合和哈希表），并报告每种类型的最大key及其大小。</li>
<li>使用<code>redis-rdb-tools</code>工具：<code>redis-rdb-tools</code> 是一种用于分析 Redis RDB 文件的工具，它可以帮助你发现大key。通过解析 Redis 的 RDB 文件，<code>redis-rdb-tools</code> 能够提供详细的统计信息，包括找到大key。</li>
</ul>
</li>
<li><p>如何处理大 key？</p>
<ul>
<li><p>删除大 key：</p>
<ul>
<li>异步删除：使用<code>UNLINK</code>命令而不是<code>DEL</code>命令。<code>UNLINK</code>是非阻塞的，会在后台删除key，从而避免阻塞Redis的主线程。</li>
<li>分批删除：对于大集合、大列表等数据结构，可以使用<code>SCAN</code>命令结合<code>SPOP</code>、<code>LPOP</code>等逐步删除内容，避免一次性删除导致阻塞。</li>
</ul>
</li>
<li><p>压缩和拆分 key</p>
<ul>
<li>当 vaule 是 string 时，比较难拆分，则使用序列化、压缩算法将 key 的大小控制在合理范围内，但是序列化和反序列化都会带来额外的性能消耗。压缩之后仍然是大 key 时，则需要进行拆分，将一个大 key 分为不同的部分，记录每个部分的 key，使用 multiget 等操作实现事务读取。</li>
<li>当 value 是 list&#x2F;set 等集合类型时，根据预估的数据规模来进行分片，不同的元素计算后分到不同的片。</li>
</ul>
</li>
</ul>
</li>
</ol>
<h1><span id="18热-key问题">18.热 key问题</span></h1><p>Redis中的“热key”问题指的是某个或某些key被频繁访问，导致这些key消耗了大量的资源（如CPU、网络带宽等），从而可能导致Redis服务器的性能瓶颈，甚至影响整个系统的稳定性。</p>
<p>检测热key：</p>
<ul>
<li><p><strong>监控命令统计</strong>：</p>
<ul>
<li>使用 <code>INFO</code> 命令监控Redis的命令统计，分析哪些命令被频繁执行，以及哪些key被频繁访问。</li>
</ul>
<p><strong>慢日志（Slow Log）</strong>：</p>
<ul>
<li>通过慢日志可以捕捉那些执行时间较长的命令，分析是否有key被频繁且耗时地操作。</li>
</ul>
<p><strong>监控请求量</strong>：</p>
<ul>
<li>通过 Redis 的 <code>MONITOR</code> 命令（适合在开发环境中使用）可以实时监控Redis服务器上所有的请求，分析哪些key被频繁访问。</li>
</ul>
</li>
</ul>
<p>处理热 key：</p>
<ul>
<li><p><strong>缓存层级化</strong>：</p>
<ul>
<li>对于特别热的key，考虑在应用层加入一层本地缓存，避免所有请求都直接打到Redis。</li>
<li>对于多级缓存架构，可以将热点数据分散到多个缓存实例中，减轻单个实例的负载。</li>
</ul>
<p><strong>数据分片（Sharding）</strong>：</p>
<ul>
<li>将热key的值进行拆分，通过分片策略将其分散到多个key中，以减少单个key的访问压力。</li>
</ul>
</li>
</ul>
<h1><span id="19为什么不建议使用redis-事务">19.为什么不建议使用Redis 事务？</span></h1><p>Redis的事务提供了一种将多个命令打包成一个原子操作的机制。然而，Redis事务并不具备传统数据库事务的所有特性，存在一些局限性和缺点，这使得在某些场景下不建议使用Redis事务。</p>
<ul>
<li>缺乏回滚机制：在Redis事务中，一旦事务中的某个命令执行失败，Redis并不会回滚之前已经成功执行的命令。所有命令都会继续执行，很可能会导致不一致状态。</li>
<li>乐观锁机制的局限：Redis事务是基于乐观锁的。如果这些键在事务执行前被其他客户端修改，事务将被中止，导致需要频繁重试，增加了实现的复杂度和不确定性。</li>
<li>事务中的命令执行并非完全原子：Redis事务的执行是串行的，即使在一个事务中发起多个命令，这些命令也会按顺序执行。如果事务中的某条命令失败，这条命令的错误不会阻止剩余命令的执行，导致数据可能进入一个不一致的状态。</li>
<li>替代方案更优：Redis的Lua脚本可以提供原子性和更复杂的操作逻辑。由于整个Lua脚本是在Redis中作为一个原子操作执行的，它比事务更安全和可靠。</li>
</ul>

        </article>
        <section class="post-near">
            <ul>
                
                    <li>上一篇: <a href="/Java17%E6%96%B0%E7%89%B9%E6%80%A7/">Java17新特性</a></li>
                
                
                    <li>下一篇: <a href="/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C2/">计算机网络2</a></li>
                
            </ul>
        </section>
        
    
        <section class="post-author">
        
            <figure class="author-avatar">
                <img src="https://sdn.geekzu.org/avatar/d22eb460ecab37fcd7205e6a3c55c228?s=200&r=X&d=" alt="Hingle" />
            </figure>
        
            <div class="author-info">
                <h4>Hingle</h4>
                <p>请在这里设置你的作者信息</p>
            </div>
        </section>
    
    </div>
</main>

    <footer>
    <div class="buttons">
        <a class="to-top" href="#"></a>
    </div>
    <div class="wrap min">
        <section class="widget">
            <div class="row">
                <div class="col-m-4">
                    <h3 class="title-recent">最新文章：</h3>
                    <ul class="post-list"><li class="post-list-item"><a class="post-list-link" href="/%E9%A1%B9%E7%9B%AE/">项目</a></li><li class="post-list-item"><a class="post-list-link" href="/MySQL2/">MySQL2</a></li><li class="post-list-item"><a class="post-list-link" href="/Java17%E6%96%B0%E7%89%B9%E6%80%A7/">Java17新特性</a></li><li class="post-list-item"><a class="post-list-link" href="/Redis2/">Redis2</a></li><li class="post-list-item"><a class="post-list-link" href="/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C2/">计算机网络2</a></li><li class="post-list-item"><a class="post-list-link" href="/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F2/">操作系统2</a></li></ul>
                </div>
            </div>
        </section>
        <section class="sub-footer">
            <p>© 2024 <a href="/">测开、Java后端最常见、最核心的面试题总结</a>. All Rights Reserved. Theme By <a href="https://github.com/Dreamer-Paul/Hingle" target="_blank" rel="nofollow">Hingle</a>.</p>
        </section>
    </div>
</footer>


<script src="/static/kico.js"></script>
<script src="/static/hingle.js"></script>


<script>var hingle = new Paul_Hingle({"copyright":false,"night":false});</script>

  </body>
</html>
